/*
 * Copyright (C) 2010 Konan Technology Inc.
 *
 * This file is part of Dokkaebi.
 *
 * Dokkaebi is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Dokkaebi is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Dokkaebi.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This software uses libraries from the FFmpeg project under the GPLv3.
 */

#include <Windows.h>
#include <tchar.h>
#include <process.h>

#include <iostream>
#include <fstream>

#include <SDL/SDL.h>

#include "Dokkaebi.h"

Dokkaebi::Dokkaebi()
{
	// must be called before using avcodec lib
	avcodec_init();
	// register all the codecs
	av_register_all();

	m_pFrame = avcodec_alloc_frame();
}

Dokkaebi::~Dokkaebi()
{
	av_free(m_pFrame);
}

bool Dokkaebi::InitJpegEncoder()
{	
	m_pOCodecCtx = avcodec_alloc_context();

	if (!m_pOCodecCtx) {
		return false;
	}
	m_pOCodecCtx->width = m_pCodecCtx->width;
	m_pOCodecCtx->height = m_pCodecCtx->height;
	m_pOCodecCtx->pix_fmt = PIX_FMT_YUVJ420P;
	m_pOCodecCtx->codec_id = CODEC_ID_MJPEG;
	m_pOCodecCtx->codec_type = CODEC_TYPE_VIDEO;
	m_pOCodecCtx->time_base.num = m_pCodecCtx->time_base.num;
	m_pOCodecCtx->time_base.den = m_pCodecCtx->time_base.den;

	m_pOCodec = avcodec_find_encoder(m_pOCodecCtx->codec_id);

	if (!m_pOCodec) {
		return false;
	}
	if (avcodec_open(m_pOCodecCtx, m_pOCodec) < 0) {
		return false;
	}
	m_pOCodecCtx->mb_lmin = m_pOCodecCtx->lmin = m_pOCodecCtx->qmin * FF_QP2LAMBDA;
	m_pOCodecCtx->mb_lmax = m_pOCodecCtx->lmax = m_pOCodecCtx->qmax * FF_QP2LAMBDA;
	m_pOCodecCtx->flags = CODEC_FLAG_QSCALE;
	m_pOCodecCtx->global_quality = m_pOCodecCtx->qmin * FF_QP2LAMBDA;

	m_pFrame->pts = 1;
	m_pFrame->quality = m_pOCodecCtx->global_quality;

	m_imgBufferSize = avpicture_get_size(PIX_FMT_YUVJ420P, 
		m_pCodecCtx->width, m_pCodecCtx->height);
	m_imgBuffer = (uint8_t *)malloc(m_imgBufferSize);

	return true;
}

bool Dokkaebi::OpenVideoStream()
{
	unsigned int		i;
	
	// find the first video stream
	m_videoStream = -1;

	for (i = 0; i < m_pFormatCtx->nb_streams; i++) {		
		if (m_pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) {
			m_videoStream = i;
			break;
		}
	}
	if (m_videoStream == -1) {
		return false;
	}
	// Get a pointer to the codec context for the video stream
	m_pCodecCtx = m_pFormatCtx->streams[m_videoStream]->codec;

	return true;
}

bool Dokkaebi::OpenCodec()
{
	AVCodec				*pCodec;

	// find the decoder for the video stream
	pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id);

	if (pCodec == NULL) {
		// could not find codec
		return false;
	}

	// inform the codec that we can handle truncated bitstreams -- i.e.,
	// bitstreams where frame boundaries can fall in the middle of packets
	if (pCodec->capabilities & CODEC_CAP_TRUNCATED) {
		m_pCodecCtx->flags |= CODEC_FLAG_TRUNCATED;
	}

	// open codec
	if (avcodec_open(m_pCodecCtx, pCodec) < 0) {
		// could not open codec
		return false;
	}
	return true;
}

bool Dokkaebi::WriteJpeg()
{
	static int				frame = 0;
	int						actualSize;	
	char					filename[1024];
	std::ofstream			ofs;
	
	sprintf(filename, "%04d.jpg", frame++);	
	ofs.open(filename, std::ios::binary);
	
	if (!ofs.is_open()) {
		return false;
	}
	// encode the frame into a jpeg image
	actualSize = avcodec_encode_video(m_pOCodecCtx, 
		m_imgBuffer, m_imgBufferSize, m_pFrame);

	if (actualSize > 0) {
		ofs.write((char *)m_imgBuffer, actualSize);
		ofs.close();

		return true;
	} else {
		ofs.close();

		return false;
	}
}

bool Dokkaebi::GetNextFrame()
{
	int					got_picture;
	AVPacket			packet;

	while (av_read_frame(m_pFormatCtx, &packet) >= 0) {
		if (packet.stream_index == m_videoStream) {
			// decode video frame
			avcodec_decode_video2(m_pCodecCtx, m_pFrame, 
				&got_picture, &packet);

			if (got_picture) {
				av_free_packet(&packet);
				return true;
			}
		}
		// av_read_frame allocates a packet
		av_free_packet(&packet);
	}	
	return false;
}

bool Dokkaebi::SkipFrames(int numFrames)
{
	AVPacket			packet;
	int					got_picture;

	for (int i = 0; i < numFrames; ) {
		if (av_read_frame(m_pFormatCtx, &packet) < 0) {
			av_free_packet(&packet);
			return false;
		} else if (packet.stream_index == m_videoStream) {
			avcodec_decode_video2(m_pCodecCtx, m_pFrame, 
				&got_picture, &packet);

			if (got_picture) {
				i++;
			}
		}
		av_free_packet(&packet);
	}
	return true;
}

bool Dokkaebi::OpenFile(const char *filename)
{
	// open video file
	if (av_open_input_file(&m_pFormatCtx, filename, NULL, 0, NULL) != 0) {
		std::cerr << "cannot open file " << filename << std::endl;
		return false;
	} else {
		// retrieve stream information
		if (av_find_stream_info(m_pFormatCtx) < 0) {
			std::cerr << "cannot find stream info" << std::endl;
			return false;
		} else {			
			dump_format(m_pFormatCtx, 0, filename, false);

			if (!OpenVideoStream()) {
				std::cerr << "couldn't find any video stream" << std::endl;
				return false;
			}
			if (!OpenCodec()) {
				std::cerr << "couldn't open codec for video stream" << std::endl;
				return false;
			}
			if (!InitJpegEncoder()) {
				std::cerr << "coutdn't initialize jpeg encoder" << std::endl;
				return false;
			}
		}
		return true;
	}
}

bool Dokkaebi::CloseFile()
{
	avcodec_close(m_pCodecCtx);
	av_close_input_file(m_pFormatCtx);

	return true;
}


// The main window class name.
static TCHAR szWindowClass[] = _T("win32app");

// The string that appears in the application's title bar.
static TCHAR szTitle[] = _T("Dokkaebi");

HINSTANCE hInst;

HWND hWnd;

// Forward declarations of functions included in this code module:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


DWORD WINAPI GenerateThumbnails(void *filename)
{
	int64_t target = 0;
	Dokkaebi *dkb = new Dokkaebi();

	if (!dkb->OpenFile((const char *)filename)) {
		return -1;
	}

	while (dkb->GetNextFrame()) {	
		target += AV_TIME_BASE;
			
		dkb->WriteJpeg();
		dkb->SkipFrames(30);
		std::cout << ".";
		SendMessage(hWnd, WM_PAINT, 0, 0);
	}		
	dkb->CloseFile();	
	delete dkb;

	// close the main window
	SendMessage(hWnd, WM_DESTROY, 0, 0);

	return 0;
}

/*
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
	if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO) < 0) {
		std::cerr << "Unable to init SDL: " << SDL_GetError() << std::endl;
		exit(1);
	}

    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL,
            _T("Call to RegisterClassEx failed!"),
            _T("Dokkaebi"),
            NULL);

        return 1;
    }

    hInst = hInstance; // Store instance handle in our global variable

    // The parameters to CreateWindow explained:
    // szWindowClass: the name of the application
    // szTitle: the text that appears in the title bar
    // WS_OVERLAPPEDWINDOW: the type of window to create
    // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
    // 500, 100: initial size (width, length)
    // NULL: the parent of this window
    // NULL: this application dows not have a menu bar
    // hInstance: the first parameter from WinMain
    // NULL: not used in this application
    hWnd = CreateWindow(
        szWindowClass,
        szTitle,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        480, 320,
        NULL,
        NULL,
        hInstance,
        NULL
    );

    if (!hWnd)
    {
        MessageBox(NULL,
            _T("Call to CreateWindow failed!"),
            _T("Dokkaebi"),
            NULL);

        return 1;
    }

    // The parameters to ShowWindow explained:
    // hWnd: the value returned from CreateWindow
    // nCmdShow: the fourth parameter from WinMain
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);	

	DWORD dwThreadId;
	HANDLE hThread = CreateThread(
		NULL,
		0,
		GenerateThumbnails,
		lpCmdLine,
		0,
		&dwThreadId);

	if (hThread == NULL) {
		ExitProcess(3);
	}
	
    // Main message loop:
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}
*/

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    TCHAR greeting[] = _T("Generating thumbnail images...");

    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);

        // Here your application is laid out.
        // For this introduction, we just print out "Hello, World!"
        // in the top left corner.
        TextOut(hdc,
            5, 5,
            greeting, _tcslen(greeting));
        // End application-specific layout section.

        EndPaint(hWnd, &ps);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }

    return 0;
}


int main(int argc, char *argv[])
{
	int64_t target = 0;
	Dokkaebi *dkb = new Dokkaebi();

	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
		std::cerr << "Unable to init SDL: " << SDL_GetError() << std::endl;
		exit(1);
	}
	SDL_Surface *screen;

    screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
        exit(1);
    }
	
	if (argc > 1) {
		if (!dkb->OpenFile(argv[1])) {
			return -1;
		}

		while (dkb->GetNextFrame()) {	
			target += AV_TIME_BASE;
			
			dkb->WriteJpeg();
			dkb->SkipFrames(30);
			std::cout << ".";
		}		
		dkb->CloseFile();
	} else {
		std::cout << "Usage: Dokkaebi.exe FILE" << std::endl;
	}
	delete dkb;

	return 0;
}